﻿using System;
using javacli;
using System.Runtime.InteropServices;
using System.Security.Principal;

using System.Diagnostics;
using System.Threading;

using negoserverjni;

namespace javacli
{
    public class FromJava
    {
        internal static TraceSource log = new TraceSource("FromJava");

        static JNI jni;
        public static void initFromJvm(IntPtr env, IntPtr classz)
        {
            jni = new JNI(env);
            JNIEnvPtr jnienv = new JNIEnvPtr(env);
            log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"before to register methods");
            jnienv.RegisterMethods(classz);
            log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"after to register methods");
            log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"CLR version: {0}", Environment.Version);
            log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"If is running as an x64 exe, size of the pointers = 8, else 4. The Size of the pointers: {0}", IntPtr.Size);
        }

        public static int Accept_sec(IntPtr env, IntPtr classz, IntPtr jtoken, IntPtr jtoken_out, long authL, IntPtr authl_out, IntPtr jName_out, IntPtr jsids, IntPtr jpresents_out, IntPtr jAuthenticationType_out)
        {
            if (jtoken == null) return (int)Status.HandshakeError;
            JNIEnvPtr jnienv = new JNIEnvPtr(env);
            byte[] token = jnienv.ByteArrayJ2C(jtoken);
            if (token == null) return (int)Status.HandshakeError;
            Spnego authenticator = null;

            try
            {
                if (authL != 0)
                {
                    IntPtr authPtr = (IntPtr)authL;
                    GCHandle h = GCHandle.FromIntPtr(authPtr);
                    authenticator = (Spnego)h.Target;
                    h.Free();
                }
                else authenticator = new Spnego();
                string Name;
                byte[] tokenToSend = authenticator.acceptSecurityContext(token, out Name);
                if (tokenToSend != null)
                {
                    jnienv.ByteArrayToRefByteArrayC2J(tokenToSend, jtoken_out);
                    log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"Accept1 tokenToSend len: {0}", tokenToSend.Length);
                }
                else
                {
                    log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"Accept1 tokenToSend null");
                }
                Status state = authenticator.State;
                if (state == Status.HandshakeInProgress)
                {
                    GCHandle h = GCHandle.Alloc(authenticator);
                    IntPtr authPtr = GCHandle.ToIntPtr(h);
                    jnienv.IntPtrToRefLongC2J(authPtr, authl_out);
                }
                else if (state == Status.HandshakeDone)
                {
                    if ((Name == null) || Name.Length == 0) log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"Accept1: Name is null");
                    else log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"Accept1: " + Name);
                    jnienv.StringToRefStringC2J(Name, jName_out);

                    int len = jnienv.GetArrayLength(jsids);
                    log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "Accept1: Len of jsids: {0}", len);
                    byte[] sid;
                    SecurityIdentifier[] ids = new SecurityIdentifier[len];
                    for (int i = 0; i < ids.Length; i++)
                    {
                        IntPtr jsid = jnienv.GetObjectArrayElement(jsids, i);
                        sid = jnienv.ByteArrayJ2C(jsid);
                        if (sid == null)
                        {
                            ids[i] = null;
                        }
                        else ids[i] = new SecurityIdentifier(sid, 0);
                    }
                    
                    User user = new User(authenticator.Identity, ids);
                    if (!user.IsAuthenticated)
                    {
                        authenticator.Dispose();
                        log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "Accept1 Before To exit: user is not authenticated");
                        return (int)Status.HandshakeError;
                    }
                    else
                    {
                        jnienv.StringToRefStringC2J(user.AuthenticationType, jAuthenticationType_out);
                        bool[] presents = user.IsMemberOfGroups();
                        jnienv.BoolArrayToRefBoolArrayC2J(presents, jpresents_out);
                        authenticator.Dispose();
                    }
                }
                else // state == Status.HandshakeError
                {
                    authenticator.Dispose();
                }
            }
            catch (Exception e)
            {
                log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,e.GetType() + " : " + e.Message);
                if (e.InnerException != null) log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,e.InnerException.GetType() + ": " + e.InnerException.Message);
                if (authenticator != null) authenticator.Dispose();
                return (int)Status.HandshakeError;
            }
            log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"Accept1 Before To exit");
            return (int)authenticator.State;


        }

        public static void Write(IntPtr env, IntPtr classz, IntPtr jstring)
        {
            if (jstring == IntPtr.Zero) return;
            //jni = new JNI(env/*, classz*/);
            JNIEnvPtr jnienv = new JNIEnvPtr(env);
            try
            {
                string towrite = jnienv.StringJ2C(jstring);
                log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, towrite);
            }
            catch (Exception e)
            {
                log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, e.GetType() + " : " + e.Message);
            }
        }
        public static IntPtr StringToArray(IntPtr env, IntPtr classz, IntPtr jstring)
        {
            if (jstring == IntPtr.Zero) return IntPtr.Zero;

            //jni = new JNI(env/*, classz*/);
            JNIEnvPtr jnienv = new JNIEnvPtr(env);
            string towrite = jnienv.StringJ2C(jstring);

            log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,towrite);

            //char[] send = towrite.ToArray<char>();
            char[] send = towrite.ToCharArray();

            return jnienv.CharArrayC2J(send);
        }

        public static IntPtr TranslateNameIntoSid(IntPtr env, IntPtr classz, IntPtr javaName)
        {
            if (javaName == IntPtr.Zero) return IntPtr.Zero;

            JNIEnvPtr jnienv = new JNIEnvPtr(env);
            string Name = jnienv.StringJ2C(javaName);

            byte[] siInbytes = Translation.TranslateNameToSid(Name);
            if (siInbytes == null) return IntPtr.Zero;
            
            IntPtr javaSid = jnienv.ByteArrayC2J(siInbytes);
            return javaSid;
        }

        public static IntPtr TranslateSidIntoName(IntPtr env, IntPtr classz, IntPtr javaSid)
        {
            JNIEnvPtr jnienv = new JNIEnvPtr(env);
            byte[] sidInbytes = jnienv.ByteArrayJ2C(javaSid);
           
            string Name = Translation.TranslateSidIntoName(sidInbytes);
            if (Name == null) return IntPtr.Zero;
            
            IntPtr javaName = jnienv.StringC2J(Name);
            return javaName;
        }

        //                                           "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;[[B[[Z)I"
        public static int Login(IntPtr env, IntPtr classz, IntPtr jname, IntPtr jpassword, IntPtr jnom_out, IntPtr jsids, IntPtr jpresents_out)
        {
            JNIEnvPtr jnienv = new JNIEnvPtr(env);
            string name = jnienv.StringJ2C(jname);
            string password = jnienv.StringJ2C(jpassword);
            LoginAuthentication login = new LoginAuthentication();
            WindowsIdentity identity = login.DoLogin(name, password);
            if (identity != null)
            {
                jnienv.StringToRefStringC2J(identity.Name, jnom_out);

                int len = jnienv.GetArrayLength(jsids);
                log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"Len of jsids : {0}", len);
                byte[] sid;
                SecurityIdentifier[] ids = new SecurityIdentifier[len];
                for (int i = 0; i < ids.Length; i++)
                {
                    IntPtr jsid = jnienv.GetObjectArrayElement(jsids, i);
                    sid = jnienv.ByteArrayJ2C(jsid);
                    if (sid == null)
                    {
                        ids[i] = null;
                    }
                    else ids[i] = new SecurityIdentifier(sid, 0);
                }
                User user = new User(identity, ids, false);

                bool[] presents = user.IsMemberOfGroups();
                identity.Dispose();
                jnienv.BoolArrayToRefBoolArrayC2J(presents, jpresents_out);

                return (int)Status.HandshakeDone;
            }
            else return (int)Status.HandshakeError;

        }

        public static void FreeAuthenticator(IntPtr env, IntPtr classz, long authL)
        {
            try
            {
                log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"FreeAuthenticator");
                if (authL != 0)
                {
                    IntPtr authPtr = (IntPtr)authL;
                    GCHandle h = GCHandle.FromIntPtr(authPtr);
                    Spnego authenticator = (Spnego)h.Target;
                    h.Free();
                    authenticator.Dispose();
                }
            }
            catch (Exception e)
            {
                log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, e.GetType() + ": " + e.Message);
            }
        }

    }
}
